Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NUT-XX and NUT-XX+1: Mint / Melt Bitcoin On-Chain #107

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

ngutech21
Copy link
Collaborator

First draft for minting and melting tokens On-Chain.

@gandlafbtc
Copy link
Collaborator

looks great so far, nice work!

question about the minting

How do we know when the payment is complete? Do we just try to proceed with mint, and see if it works? Or is there a way to look up the paid status of a mintQuote?

Also, is there a way to let the client know, how many more confs are required, before the mint can take place?

Last question, what happens if the tx confirmation time exceeds the expiry?

17.md Outdated Show resolved Hide resolved
17.md Outdated Show resolved Hide resolved
18.md Outdated
Comment on lines 45 to 46
The mint can return multiple `PostMeltQuoteBtcOnchainResponse` with different `fees` and `expiry` dates. The wallet can choose which one to pay and the other ones will expire. Where `quote` is the quote ID, `amount` the amount that needs to be provided, and `fee` the additional fee that is required. The mint expects `Alice` to include `Proofs` of *at least* `total_amount = amount + fee`. `paid` indicates whether the request as been paid and `expiry` is the Unix timestamp until which the melt quote is valid.

Copy link
Contributor

@callebtc callebtc Mar 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth mentioning that there are (at least) two ways of handing the variable fee rate problem here.

One option could be that the wallet chooses a fee in PostMeltQuoteBtcOnchainRequest this would be a bit like a Lightning node choosing the max_fee for a payment. The mint could then respond with a response or an error depending on whether it agrees. I think this would have to accompanied with some info setting where the mint announces it's accepted fee range.

The other option, as you've proposed, would be for the mint to limit the users's choices by returning multiple melt quotes.

Did you consider the first option as well? I wonder if it has any benefits. Happy to hear thoughts from others on this as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting idea. No haven't thought about it yet.

@callebtc
Copy link
Contributor

How do we know when the payment is complete? Do we just try to proceed with mint, and see if it works? Or is there a way to look up the paid status of a mintQuote?

You can use the following endpoint as per 17.md:

GET https://mint.host:3338/v1/mint/quote/btconchain/{quote_id}

Also, is there a way to let the client know, how many more confs are required, before the mint can take place?

This would be cool but I suspect it won't be generally possible to determine this number with every on-chain backend. Take for example a service like Strike or Blink. Obviously, this would be possible with a bitcoin core, lnd, cln, ... on-chain wallet.

what happens if the tx confirmation time exceeds the expiry?

That's a very good question. While LN invoices can have a clear expiry, on-chain is a bit more tricky. It might be worth to consider removing this from onchain mints. I think melt expiry doesn't have this problem.

17.md Outdated
"quote": <str>,
"address": <str>,
"paid": <bool>,
"expiry": <int>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This expiry field might be problematic (cf @gandlafbtc's comment).

@callebtc
Copy link
Contributor

callebtc commented Mar 29, 2024

Awesome NUT, feels complete! Looking forward to implementing it.

Left a few comments, I don't see any issues other than the expiry field for minting.

18.md Outdated Show resolved Hide resolved
Co-authored-by: thesimplekid <[email protected]>
@ngutech21
Copy link
Collaborator Author

what happens if the tx confirmation time exceeds the expiry?

That's a very good question. While LN invoices can have a clear expiry, on-chain is a bit more tricky. It might be worth to consider removing this from onchain mints. I think melt expiry doesn't have this problem.

Good point. I think it's problematic in both way: A wallet could choose a fee that is to low to get in the next block before the quote expires.
On the other side it might be problematic to have a quote that never expires. The way I see a quote is an offer or a contract between the mint and the wallet. If the quote would never expire, the offer is still valid and it would not be possible for a mint to switch to a different onchain backend. I know this is a very rare case. Maybe it would be a good idea to make the expiry optional and recommend a long expiry like 14 days as default for the mint. Any other suggestions?

@thesimplekid
Copy link
Collaborator

thesimplekid commented Mar 31, 2024

Maybe it would be a good idea to make the expiry optional and recommend a long expiry like 14 days as default for the mint.

Worth noting that for sat denominated quotes a long expiry is fine, but for units with an exchange rate (ie sat/usd) it introduces some risk to the mint where wallets could attempt to arbitrage the exchange rate since its up to them to execute the quote or not so i don't think long expiry times should be recommended for non sat units.

EDIT: Is this NUT limited to only the sat unit like NUT04? if so the above can be ignored.

17.md Outdated Show resolved Hide resolved
18.md Outdated Show resolved Hide resolved
18.md Outdated Show resolved Hide resolved
18.md Outdated Show resolved Hide resolved
17.md Outdated Show resolved Hide resolved
@elnosh

This comment was marked as resolved.

18.md Outdated
Comment on lines 168 to 177
GET https://mint.host:3338/v1/melt/btconchain/{tx_id}
```

The mint `Bob` responds with a `GetMeltBtcOnchainResponse`.

```json
{
"paid": false
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this paid field different than calling GET /v1/melt/quote/btconchain/{quote_id} and getting that paid status there?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be removed. I don't see any benefit over just using GET /v1/melt/quote/btconchain/{quote_id}.

A related open question I have is should the mint broadcast the transaction as soon as it receives valid proofs for the melt, or can the mint decide to wait and include it in a batch of multiple melts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be removed. I don't see any benefit over just using GET /v1/melt/quote/btconchain/{quote_id}.

yeah

A related open question I have is should the mint broadcast the transaction as soon as it receives valid proofs for the melt, or can the mint decide to wait and include it in a batch of multiple melts?

interesting. That will require some changes to the response of the quote state call

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the redundant GET https://mint.host:3338/v1/melt/btconchain/{tx_id} endpoint

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be removed. I don't see any benefit over just using GET /v1/melt/quote/btconchain/{quote_id}.

A related open question I have is should the mint broadcast the transaction as soon as it receives valid proofs for the melt, or can the mint decide to wait and include it in a batch of multiple melts?

IMO the mint can decide if it wants to batch transaction or not. This is same as centralized btc exchanges handle this. We could add a property to the info-endpoint to signal that a mint might delay the melt for batching, but I don't think this is necessary

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the mint can decide if it wants to batch transaction or not. This is same as centralized btc exchanges handle this. We could add a property to the info-endpoint to signal that a mint might delay the melt for batching, but I don't think this is necessary

I think we do need to signal this and it needs to be more concrete then the mint may delay. I would suggest we add a time component to the MeltQuoteResponse this way if the user wants the withdrawal more quickly they will need to pay a higher fee if they are willing to wait and allow the mint to batch they can select a Quote with a lower fee and wait longer.

Allowing the mint to delay for batching but not defining what a delay means will lead to a bad user experience where they don't know when their transaction will be process and they may need it immediately and be willing to pay for that.

@thesimplekid

This comment was marked as resolved.

ngutech21 and others added 2 commits June 18, 2024 15:38
Co-authored-by: thesimplekid <[email protected]>
Co-authored-by: thesimplekid <[email protected]>
@callebtc callebtc changed the title NUT-17 and NUT-18: Mint / Melt Bitcoin On-Chain NUT-XX and NUT-XX+1: Mint / Melt Bitcoin On-Chain Jun 18, 2024
18.md Outdated Show resolved Hide resolved
@EthnTuttle
Copy link
Contributor

No idea if this is relevant but was reviewing it and thought of this thread/PR/NUT.

The deposit flow in fedimint places the responsibility on the client to prove that a deposit is spendable by the mint. The client achieves this by generating a tweak applied to the mint's peg-in descriptor and sends funds to the tweaked address. Once the client observes enough confirmations, it will prove to the mint that the coins are spendable with the tweak and the merkle proof that the tx was included in a block. If the proof is valid, the mint will issue ecash for the deposit. With this flow, the mint won't be able to correlate user deposit activity if a new deposit address is used.
fedimint/fedimint#5473 (comment)

17.md Outdated
```json
{
"amount": <int>,
"unit": <str_enum["sat"]>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"unit": <str_enum["sat"]>

Note: All PostMintQuoteBtcOnchainRequest's are denominated in sat.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should we remove the unit-field from the PostMintQuoteOnchainRequest? If the mint uses blink as a backend for handling the onchain transactions it can support onchain snd/receive with usd as a currency. Hopefully in the future there will be similar services. https://dev.blink.sv/api/usd-onchain-send

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a question of scoping this NUT. To support other units where a conversion rate is involved how expiration is handled as well as how confirmation time is defined is much more important as the conversion rate could change outside an acceptable slippage. So in order to support other units how confirmation and expiration is defined needs to be included as well.

17.md Outdated Show resolved Hide resolved
17.md Outdated Show resolved Hide resolved
17.md Outdated Show resolved Hide resolved
18.md Outdated Show resolved Hide resolved
18.md Outdated Show resolved Hide resolved
18.md Outdated Show resolved Hide resolved
18.md Outdated Show resolved Hide resolved
@ngutech21
Copy link
Collaborator Author

what happens if the tx confirmation time exceeds the expiry?

That's a very good question. While LN invoices can have a clear expiry, on-chain is a bit more tricky. It might be worth to consider removing this from onchain mints. I think melt expiry doesn't have this problem.

Good point. I think it's problematic in both way: A wallet could choose a fee that is to low to get in the next block before the quote expires. On the other side it might be problematic to have a quote that never expires. The way I see a quote is an offer or a contract between the mint and the wallet. If the quote would never expire, the offer is still valid and it would not be possible for a mint to switch to a different onchain backend. I know this is a very rare case. Maybe it would be a good idea to make the expiry optional and recommend a long expiry like 14 days as default for the mint. Any other suggestions?

So what should we do? I think it would be best to change the expiry for mint-requests to optional so a mint can use a expiry for usd based mints and no expiry for sat based mint requests? Any other suggestions @gandlafbtc @callebtc @thesimplekid

@ngutech21 ngutech21 marked this pull request as ready for review July 28, 2024 14:48
@thesimplekid
Copy link
Collaborator

The issue with on chain is there is no way to expire an address unlike ln, so how is a deposit after an expiration handled is it considered a donation to the mint? If that is that case I think that needs to be made explicit. I think this maybe okay since even in the case of removing expiration that is effectively the case if a deposit is made to a mint that is no longer in operation (if the mint operator still has the keys), so having the explicit handling of expiration might be an improvement.

Of course there is the opportunity for a benevolent mint operator to settle a quote paid after out of band if they are known or have contact info set in the NUT-06. I noticed this is how the strike api handles it, though that has its own trade offs.

As i mentioned above definition of confirmation needs to be included as well with the expiration to signal what needs to happen before the expiration in order for the quote to be settled, num blocks conf, transaction broadcast. This could even be split for example transaction needs to be broadcast before x time to get the exchange rate, but ecash wont be issued until x confirmations.

@ngutech21
Copy link
Collaborator Author

should there be a way for mints to charge a fee upfront in a mint quote request?

How do we know when the payment is complete? Do we just try to proceed with mint, and see if it works? Or is there a way to look up the paid status of a mintQuote?
Also, is there a way to let the client know, how many more confs are required, before the mint can take place?

regarding these questions, would it help or are there any concerns if the mint responded with the number of confirmations it requires to consider the mint quote paid?

Yes, good point. I have added min_confirmations to the info-endpoint

"address": "bc1qkyfgd7mus7ykfd7qkwakq75qsf7rtm...",
"state": "UNPAID",
"expiry": 1701704757
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth adding the txid here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might though this question made me think what if a a quote is payed with multiple transaction, I think that should still be valid? So to cover that it would need to be a list of txid? Unless we explicitly say it MUST be paid in one tx.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be explicit only one transaction is supported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is always going to be possible to make multiple transactions paying the same address, so someone will do that... so how to handle? Multiple transactions also make reorg risk more complex and add consolidation cost for the Mint.

Feels resonable that the requirement should be for a single transaction with one output paying the mint, anything else and the Mint can 'fail' the mint request and refund whatever it got somehow -- would need the user to provide a refund address, or the mint would have to issue an ecash claim on it (but then, consolidation cost etc., would be a 'mint with on-chain dust and then drain the mint's lightning channels' attackable behaviour)

```

The wallet **MUST** store the `amount` in the request and the `quote` id in the response in its database so it can later request the tokens after paying the request. After payment, the wallet continues with the next section.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be worth adding a note here that wallets SHOULD display the quote as a BIP21 URI or BIP21 URI encoded as a QR code to the user. This would help reduce errors of underpaying or overpaying accidentally.

NOTE: There is a draft to replace BIP21 bitcoin/bips#1555 but as its backwards compatible i suggest we say bip21 for now and can update later.

@prusnak
Copy link
Collaborator

prusnak commented Aug 19, 2024

I wonder if we want to allow minting/melting on Liquid chain and if yes, then whether it should be part of these two nuts or a new pair of nuts.

@davidcaseria
Copy link

I wonder if we want to allow minting/melting on Liquid chain and if yes, then whether it should be part of these two nuts or a new pair of nuts.

I think it makes more sense to keep separate NUTS, especially since its support should be signaled differently by NUT-06.

@prusnak
Copy link
Collaborator

prusnak commented Aug 24, 2024

especially since its support should be signaled differently by NUT-06.

Ah yes, that is indeed a good reason.

@davidcaseria
Copy link

It does not necessarily impact the spec, but something like this should be the recommended way to handle address generation: https://blog.zaprite.com/optimizing-address-usage-for-the-gap-limit/

Comment on lines +33 to +45
```json
[
{
"quote": <str>,
"description": <str|null>,
"amount": <int>,
"fee": <int>,
"state": <str_enum[STATE]>,
"expiry": <int>
}
]
```
The mint can return multiple `PostMeltQuoteBtcOnchainResponse` with different `fees` and `expiry` dates. The wallet can choose which one to pay and the other ones will expire. Where `quote` is the quote ID, `amount` the amount that needs to be provided, and `fee` the additional fee that is required. The mint expects `Alice` to include `Proofs` of *at least* `total_amount = amount + fee`. `paid` indicates whether the request as been paid and `expiry` is the Unix timestamp until which the melt quote is valid.
Copy link
Collaborator

@thesimplekid thesimplekid Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It maybe better if we express fee in sats but also with a time component. This would allow the mint to batch transactions. For example fee of x sats will be paid within the hour, fee of y sats within the day etc. This leaves it up to the mint to actually set the final sats per vbyte to meet whatever time period they agreed to in the quote.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the client specify how fast they want the transaction confirmed in the quote request (e.g., with a target_blocks field)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As its currently written the mint returns a list of quotes with different fees for different speed of confirmation. That could be flipped and the client tells the mint what it wants and the mint returns only one quote for that target.

I lean towards how its currently written where the mints returns a set of options and then the client picks the one that best suits it. But as i comments i think we need a time component in addition to the fee so that could be expressed in block time.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the spec offer guidance to the mint implementors on what fee rates should be returned so wallet experiences can be similar even though the wallet isn't in control of requesting its fee rate?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the spec offer guidance to the mint implementors on what fee rates should be returned so wallet experiences can be similar even though the wallet isn't in control of requesting its fee rate?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what fee rates should be returned

Surely the mint should be able to pick whatever fee it wants? Needs to be flexible to feerate market swings, but also some mints might want to disincentivise on-chain melts by adding a premium, others might offer it at cost

Comment on lines +20 to +37
```json
{
"amount": <int>,
"unit": <str_enum["sat"]>
}
```
with the requested `amount` and the `unit`.

The mint `Bob` then responds with a `PostMintQuoteBtcOnchainResponse`:

```json
{
"quote": <str>,
"address": <str>,
"state": <str_enum[STATE]>,
"expiry": <int>
}
```
Copy link
Collaborator

@thesimplekid thesimplekid Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If unit is included here don't we need to add an amount in sats to the quote response so the mint can signal the exchange rate. Otherwise if it is non bitcoin unit the wallet will not know how much bitcoin to send to the address to fill the quote.

That being said because of the imprecise nature of sending a bitcoin onchain transaction I would be for removing the unit and limiting it to sat denominated quotes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I support keeping it simple to start and only supporting sats as the unit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels simplest to say

  1. You can only mint BTC on-chain (sats only, no other units)
  2. You can then swap sats ecash for ecash in a different unit with the mint

So if you wanted to get e.g. "USD eCash by making an onchain tx", it's a two-step process. Has the advantage of a swap being atomic, much faster and less flaky than a mint depending on N confirmations

`state` is an enum string field with possible values `"UNPAID"`, `"PAID"`, `"PENDING"`, `"ISSUED"`:
- `"UNPAID"` means that the quote's request has not been paid yet.
- `"PAID"` means that the request has been paid.
- `"PENDING"` means that the quote is currently being issued.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this when the transaction is still confirming?

"address": "bc1qkyfgd7mus7ykfd7qkwakq75qsf7rtm...",
"state": "UNPAID",
"expiry": 1701704757
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we return the number of confirmations the transaction currently has?

Comment on lines +33 to +45
```json
[
{
"quote": <str>,
"description": <str|null>,
"amount": <int>,
"fee": <int>,
"state": <str_enum[STATE]>,
"expiry": <int>
}
]
```
The mint can return multiple `PostMeltQuoteBtcOnchainResponse` with different `fees` and `expiry` dates. The wallet can choose which one to pay and the other ones will expire. Where `quote` is the quote ID, `amount` the amount that needs to be provided, and `fee` the additional fee that is required. The mint expects `Alice` to include `Proofs` of *at least* `total_amount = amount + fee`. `paid` indicates whether the request as been paid and `expiry` is the Unix timestamp until which the melt quote is valid.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the client specify how fast they want the transaction confirmed in the quote request (e.g., with a target_blocks field)?

@starbackr-dev
Copy link

All, Is there an implementation of this on-chain mint/melt? I'm thinking of starting one with nutshell. Checking to make sure I'm not duplicating work.

@thesimplekid
Copy link
Collaborator

All, Is there an implementation of this on-chain mint/melt? I'm thinking of starting one with nutshell. Checking to make sure I'm not duplicating work.

PR for cdk is here cashubtc/cdk#172 haven't seen anything for nutshell.

@starbackr-dev
Copy link

All, Is there an implementation of this on-chain mint/melt? I'm thinking of starting one with nutshell. Checking to make sure I'm not duplicating work.

PR for cdk is here cashubtc/cdk#172 haven't seen anything for nutshell.

great. thanks. Will start to work on Nutshell.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new nut A new protocol NUT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants